package com.gitee.feizns.web.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.entity.Columns;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.gitee.feizns.ArrayUtils;
import com.gitee.feizns.StringUtils;
import com.gitee.feizns.bean.BeanUtils;
import com.gitee.feizns.bean.Property;
import com.gitee.feizns.bean.PropertyUtils;
import com.gitee.feizns.reflect.ConstructorUtils;
import com.gitee.feizns.web.dao.ar.AbstractModel;
import com.gitee.feizns.web.dao.ar.annotation.Alias;
import com.gitee.feizns.web.dao.ar.annotation.Ignore;
import com.gitee.feizns.web.dao.ar.annotation.JSONAry;
import com.gitee.feizns.web.dao.ar.annotation.JSONObj;
import com.gitee.feizns.web.dao.ar.annotation.condition.Eq;
import com.gitee.feizns.web.dao.ar.annotation.condition.Ge;
import com.gitee.feizns.web.dao.ar.annotation.condition.Gt;
import com.gitee.feizns.web.dao.ar.annotation.condition.In;
import com.gitee.feizns.web.dao.ar.annotation.condition.Last;
import com.gitee.feizns.web.dao.ar.annotation.condition.Le;
import com.gitee.feizns.web.dao.ar.annotation.condition.Like;
import com.gitee.feizns.web.dao.ar.annotation.condition.Lt;
import com.gitee.feizns.web.dao.ar.annotation.condition.OrderBy;
import com.gitee.feizns.web.dao.ar.annotation.condition.Where;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author feizns
 * @since 2019/8/3 0003
 */
public abstract class ModelUtils {

    /**
     * 映射到vo
     * @param targetType
     * @param original
     * @param <T>
     * @return
     */
    public static <T> T mapVo(Class<T> targetType, Object original) {
        if ( original != null ) {
            if ( targetType.isAssignableFrom(original.getClass()) ) {
                return (T) original;
            } else {
                T instance = ConstructorUtils.newInstance(targetType);
                Map<String, Object> map;
                if ( Map.class.isAssignableFrom(original.getClass()) )
                    map = (Map<String, Object>) original;
                else
                    map = BeanUtils.toMap(original);
                PropertyUtils
                        .rwProps(instance)
                        .forEach(p -> {
                            Alias alias = p.getAnnotation(Alias.class);
                            String pName = alias != null ? StringUtils.underlineToHump(alias.value()) : p.name();
                            Object val = map.get(pName);
                            if ( val != null && val.getClass() == String.class ) {
                                Field field = p.getField();
                                if ( field != null ) {
                                    if ( p.isAnnotationPresent(JSONObj.class) )
                                        val = JSONObject.parseObject((String) val, field.getType());
                                    else if ( p.isAnnotationPresent(JSONAry.class) ) {
                                        Class genericType = getFieldFirstGenericType(field);
                                        if ( genericType != null )
                                            val = JSONArray.parseArray((String) val, genericType);
                                        else
                                            val = JSONArray.parseArray((String) val);
                                    }
                                }
                            }
                            p.set(val);
                        });
                return instance;
            }
        }
        return null;
    }

    private static Class getFieldFirstGenericType(Field field) {
        try {
            Type type = field.getGenericType();
            Class t = (Class) ((ParameterizedType) type).getActualTypeArguments()[0];
            return t;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 下划线的主键名
     * @param model
     * @return
     */
    public static String underlinePkName(AbstractModel model) {
        return StringUtils.toUnderline(model.pkField().getName());
    }

    /**
     * 搜集不为null的列条件对应
     * @param obj
     * @return
     */
    public static Map<String, Object> underlineMapCondition(Object obj) {
        return PropertyUtils.rwProps(obj)
                .filter(p -> p.val() != null)
                .collect(Collectors.toMap(p -> StringUtils.toUnderline(p.name()), Property::val));
    }

    /**
     * 创建对应实体中的所有列
     * @param type
     * @return
     */
    public static Columns createUnderlineColumns(Class<?> type) {
        return createColumns(type, StringUtils::toUnderline, StringUtils::toUnderline);
    }

    /**
     * 下划线转驼峰的 select 映射
     * @param type
     * @return
     */
    public static Columns createUnderlineAsHumpColumns(Class<?> type) {
        return createColumns(type, StringUtils::toUnderline, Function.identity());
    }

    /**
     * 自定义映射规则
     * @param type
     * @param colMap
     * @return
     */
    public static Columns createColumns(Class<?> type, Function<String, String> colMap, Function<String, String> asMap) {
        Columns columns = Columns.create();
        PropertyUtils
                .rwProps(ConstructorUtils.newInstance(type))
                .filter(p -> p.isAnnotationPresent(Ignore.class) == false)
                .forEach(p -> {
                    Alias alias = p.getAnnotation(Alias.class);
                    String pName = p.name();
                    if ( alias != null )
                        pName = alias.value();
                    columns.column(colMap.apply(pName), asMap.apply(pName));
                });
        return columns;
    }

    /**
     * 根据条件注解来创建wrapper
     * @param condition
     * @param defaultHandle
     * @return
     */
    public static <T> Wrapper<T> condition(Object condition, BiConsumer<Property<Object, Object>, Wrapper<T>> defaultHandle) {
        Wrapper<T> ret = new EntityWrapper<>();

        PropertyUtils
                .props(condition)
                .filter(p -> p.isAnnotationPresent(Ignore.class) == false)
                .filter(o -> o.readMethod().getDeclaringClass() != Object.class)
                .forEach(p -> handleProp(p, ret, defaultHandle));

        Class<?> cClz = condition.getClass();
        OrderBy order = cClz.getAnnotation(OrderBy.class);

        //默认填充降序
        if ( order != null ) {
            String[] value = order.value();
            boolean[] desc = order.desc();
            if ( value.length >= desc.length ) {
                boolean[] newDesc = new boolean[value.length];
                for (int i = 0; i < desc.length; i++)
                    newDesc[i] = desc[i];

                for (int i = desc.length; i < newDesc.length; i++)
                    newDesc[i] = true;
                desc = newDesc;
            }

            for (int i = 0; i < value.length; i++)
                ret.orderBy(StringUtils.toUnderline(value[i]), !desc[i]);
        }

        Where where = cClz.getAnnotation(Where.class);
        if ( where != null )
            ret.and(where.value());

        Last last = cClz.getAnnotation(Last.class);
        if ( last != null )
            ret.last(last.value());

        return ret;
    }

    /**
     * 根据条件创建wrapper 默认什么都不做
     * @param condition
     * @return
     */
    public static <T> Wrapper<T> condition(Object condition) {
        return condition(condition, null);
    }

    /**
     * 没有注解的话，就等值匹配
     * @param condition
     * @return
     */
    public static <T> Wrapper<T> conditionDefaultEq(Object condition) {
        return condition(condition, (prop, wrapper) -> {
            wrapper.eq(fieldName(prop), prop.val());
        });
    }

    /**
     * 没有注解的默认等值匹配
     * @param prop
     * @param wrapper
     * @return
     */
    private static <T> Wrapper<T> handleProp(Property<Object, Object> prop, Wrapper<T> wrapper, BiConsumer<Property<Object, Object>, Wrapper<T>> defaultHandle) {
        Object val = prop.val();
        if ( isNotEmpty(val) && prop.isAnnotationPresent(Ignore.class) == false ) {
            if ( prop.isAnnotationPresent(Ge.class) )
                wrapper.ge(fieldName(prop, prop.getAnnotation(Ge.class).value()), val);
            else if ( prop.isAnnotationPresent(Gt.class) )
                wrapper.gt(fieldName(prop, prop.getAnnotation(Gt.class).value()), val);
            else if ( prop.isAnnotationPresent(In.class) ) {
                Object[] array = ArrayUtils.toArray(val);
                if ( array.length == 1 )
                    wrapper.eq(fieldName(prop, prop.getAnnotation(In.class).value()), array[0]);
                else
                    wrapper.in(fieldName(prop, prop.getAnnotation(In.class).value()), array);
            }
            else if ( prop.isAnnotationPresent(Le.class) )
                wrapper.le(fieldName(prop, prop.getAnnotation(Le.class).value()), val);
            else if ( prop.isAnnotationPresent(Lt.class) )
                wrapper.lt(fieldName(prop, prop.getAnnotation(Lt.class).value()), val);
            else if ( prop.isAnnotationPresent(Like.class) ) {
                Like like = prop.getAnnotation(Like.class);
                if ( like.value().length == 0 ) {
                    wrapper.like(true, StringUtils.toUnderline(prop.name()), prop.val(String.class), like.type());
                } else if ( like.value().length == 1 ){
                    wrapper.like(true, fieldName(prop, like.value()[0]), prop.val(String.class), like.type());
                } else if ( like.value().length > 1 ){
                    wrapper.like(true, concat(like.value()), prop.val(String.class), like.type());
                }
            }
            else if ( prop.isAnnotationPresent(Eq.class) )
                wrapper.eq(fieldName(prop, prop.getAnnotation(Eq.class).value()), val);
            else if ( prop.isAnnotationPresent(Where.class) )
                    wrapper.where(prop.getAnnotation(Where.class).value(), val);
            else
                Optional.ofNullable(defaultHandle).ifPresent(c -> c.accept(prop, wrapper));
        }
        return wrapper;
    }

    private static String concat(String... val) {
        StringBuilder builder = new StringBuilder("CONCAT(");
        for (int i = 0; i < val.length; i++) {
            builder.append(String.format("IFNULL(%s,'')", StringUtils.toUnderline(val[i])));
            builder.append(",");
        }
        builder.setLength(builder.length() - 1);
        builder.append(")");
        return builder.toString();
    }

    public static String fieldName(Property prop, String name) {
        return StringUtils.toUnderline(StringUtils.isEmpty(name) ? prop.name() : name);
    }

    public static String fieldName(Property prop) {
        return StringUtils.toUnderline(prop.name());
    }

    private static boolean isNotEmpty(Object val) {
        if ( val != null ) {
            if ( val.getClass() == String.class )
                return StringUtils.isEmptyOrBlank((String) val) == false;
            else if ( Collection.class.isAssignableFrom(val.getClass()) )
                return ((Collection) (val)).isEmpty() == false;
            else if ( val.getClass().isArray() )
                return Array.getLength(val) != 0;
            return true;
        }
        return false;
    }

}
